package org.origin.centre.security.configs;

import org.origin.centre.security.authentication.converters.PasswordAuthenticationConverter;
import org.origin.centre.security.authentication.handlers.IAuthenticationFailureHandler;
import org.origin.centre.security.authentication.handlers.IAuthenticationSuccessHandler;
import org.origin.centre.security.authentication.providers.PasswordAuthenticationProvider;
import org.origin.centre.security.properties.AuthorizationProperty;
import org.origin.centre.security.token.JwtTokenCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.web.SecurityFilterChain;

import javax.sql.DataSource;

/**
 * spring authorization 密码模式
 * OAuth2资源服务器配置
 * 我们的上下文中的资源是我们为粗暴操作公开的REST API。
 * 要访问这些资源，必须对客户端进行身份验证。
 * 在实时场景中，每当用户尝试访问这些资源时，都会要求用户提供他真实性，
 * 一旦用户被授权，他将被允许访问这些受保护的资源。
 * 可以参考博客1：@link{https://blog.csdn.net/xiyang_1990/article/details/132223393}
 * 可以参考博客2：@link{https://blog.csdn.net/u013737132/article/details/134024381}
 * 可以参考官方示例：@link{https://github.com/spring-projects/spring-authorization-server/tree/main/samples/demo-authorizationserver}https://github.com/spring-projects/spring-authorization-server/tree/main/samples/demo-authorizationserver
 *
 * @author ferret
 * @version 2024-05-08
 */
@SuppressWarnings({"unused", "SpringFacetCodeInspection", "SpringJavaInjectionPointsAutowiringInspection"})
@Configuration(proxyBeanMethods = false)
public class AutoAuthorizationConfig {
    protected final AuthorizationProperty property;

    public AutoAuthorizationConfig(AuthorizationProperty property) {
        this.property = property;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
                                                                      UserDetailsService userDetailsService,
                                                                      OAuth2TokenGenerator<?> tokenGenerator,
                                                                      AuthenticationManager authenticationManager,
                                                                      AuthorizationServerSettings authorizationServerSettings,
                                                                      RegisteredClientRepository registeredClientRepository,
                                                                      OAuth2AuthorizationService oauth2AuthorizationService,
                                                                      OAuth2AuthorizationConsentService oauth2AuthorizationConsentService) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);

        authorizationServerConfigurer
                // 管理客户端
                .registeredClientRepository(registeredClientRepository)
                // 管理授权 默认是内存模式的
                .authorizationService(oauth2AuthorizationService)
                // 用于管理新的和现有的授权同意。
                .authorizationConsentService(oauth2AuthorizationConsentService)
                // 授权服务器的设置 其实就是默认路径的管理 /oauth2/token /oauth2/authorize等
                .authorizationServerSettings(authorizationServerSettings)
                .tokenEndpoint(tokenEndpoint ->
                        tokenEndpoint
                                // 使用默认的 converters
                                .accessTokenRequestConverters(authenticationConverters -> {
                                    authenticationConverters.add(new PasswordAuthenticationConverter());
                                })
                                // 使用默认的 providers
                                .authenticationProviders(authenticationProviders -> {
                                    authenticationProviders.add(new PasswordAuthenticationProvider(authenticationManager, oauth2AuthorizationService, tokenGenerator));
                                })
                                // 自定义成功响应
                                .accessTokenResponseHandler(new IAuthenticationSuccessHandler())
                                // 自定义失败响应
                                .errorResponseHandler(new IAuthenticationFailureHandler())
                );

        http.userDetailsService(userDetailsService);
        return http.build();
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository(DataSource dataSource) {
        return new JdbcRegisteredClientRepository(new JdbcTemplate(dataSource));
    }

    @Bean
    public OAuth2AuthorizationService oauth2AuthorizationService(DataSource dataSource, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(new JdbcTemplate(dataSource), registeredClientRepository);
    }

    @Bean
    public OAuth2AuthorizationConsentService oauth2AuthorizationConsentService(DataSource dataSource, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(new JdbcTemplate(dataSource), registeredClientRepository);
    }

    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer(RedisTemplate<Object, Object> redisTemplate, AuthorizationProperty property) {
        return new JwtTokenCustomizer(redisTemplate, property.getTokenCacheKKey());
    }

    @Bean
    public OAuth2TokenGenerator<?> tokenGenerator(JwtEncoder jwtEncoder, OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer) {
        JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
        jwtGenerator.setJwtCustomizer(jwtCustomizer);
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(
                jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
    }
}